package cn.mw.cmdb.task;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.mw.cmdb.entity.*;
import cn.mw.cmdb.enums.BatchScanStatusEnum;
import cn.mw.cmdb.mongoMapper.AssetsDiscoverMapper;
import cn.mw.cmdb.mongoMapper.AssetsScanResultMapper;
import cn.mw.cmdb.mongoMapper.AssetsScanTotalResultMapper;
import cn.mw.cmdb.param.*;
import cn.mw.cmdb.service.AssetsManageService;
import cn.mw.cmdb.service.ServiceAction;
import cn.mw.cmdb.service.impl.AssetsScanResultServiceImpl;
import cn.mw.cmdb.service.impl.CmdbServiceManage;
import cn.mw.cmdb.service.impl.MwCustomRuleServiceImpl;
import cn.mw.cmdb.util.BaseUtil;
import cn.mw.cmdb.util.CMDBTool;
import cn.mw.components.mongodb.tool.CriteriaWrapper;
import cn.mw.microMonitorCommon.api.scan.ScanResult;
import cn.mw.microMonitorCommon.security.utils.SecurityUtils;
import cn.mw.microMonitorCommon.util.IDModelType;
import cn.mw.microMonitorCommon.util.IpUtil;
import cn.mw.microMonitorCommon.util.ModuleIDManager;
import cn.mw.microMonitorCommon.util.SpringUtils;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

import java.time.Instant;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static cn.mw.cmdb.config.MQTT.OnlineStateEnum.offline;
import static cn.mw.cmdb.config.MQTT.OnlineStateEnum.online;
import static cn.mw.cmdb.param.MwDiscoveryExecutorTypeEnum.autoAdd;
import static cn.mw.cmdb.param.MwDiscoveryExecutorTypeEnum.autoManage;
import static cn.mw.cmdb.util.CMDBTool.getFieldName;
import static cn.mw.components.mongodb.entity.MongoPropertyType.strValueConvert;

@Slf4j
public class AssetsDiscoveryTask extends Thread {

    private AssetsManageService assetsManageService;
    private AssetsDiscoveryRunManage runManage;

    private Authentication authentication;

    private CmdbServiceManage cmdbServiceManage;

    private AssetsDiscoverInfo assetsDiscoverInfo;

    private AssetsScanParam scanParam;

    private int batchNo;

    private AssetsScanResultMapper detailMapper;

    private AssetsScanTotalResultMapper totalResultMapper;

    private AssetsDiscoverMapper assetsDiscoverMapper;

    private Thread statusRefreshThread;

    // true: 资产扫描   false: 失败任务扫描
    private boolean scanType;

    public AssetsDiscoveryTask(Authentication authentication, int batchNo, AssetsManageService assetsManageService
            , AssetsDiscoveryRunManage runManage, AssetsScanResultMapper detailMapper, AssetsDiscoverMapper assetsDiscoverMapper
            , AssetsDiscoverInfo assetsDiscoverInfo, AssetsScanParam scanParam, boolean scanType, CmdbServiceManage cmdbServiceManage) {
        this.batchNo = batchNo;
        this.assetsManageService = assetsManageService;
        this.runManage = runManage;
        this.detailMapper = detailMapper;
        this.assetsDiscoverMapper = assetsDiscoverMapper;
        this.authentication = authentication;
        this.assetsDiscoverInfo = assetsDiscoverInfo;
        this.scanParam = scanParam;
        this.scanType = scanType;
        this.cmdbServiceManage = cmdbServiceManage;
        this.totalResultMapper = SpringUtils.getBean(AssetsScanTotalResultMapper.class);
    }

    @Override
    public void run() {

        long epochSecond = Instant.now().toEpochMilli();
        Long userId = null;
        String id = assetsDiscoverInfo.getId();
        try {
            runManage.startDiscovery(assetsDiscoverInfo);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            userId = SecurityUtils.getUserId();
            statusRefreshThread = new Thread(new AssetsDiscoveryStatusRefreshTask(runManage, assetsDiscoverInfo));
            statusRefreshThread.start();
            ScanResult scanResult = assetsManageService.assetScan(scanParam);
            scanResult.setTaskId(scanParam.getTaskId());
            assetsDiscoverInfo.setStatus(BatchScanStatusEnum.success.name());

            //保存扫描结果
            AssetsScanResultInfo assetsScanResult = convertAssetsScanList(scanResult);
            if (this.scanType) {
                assetsScanResult.setBatchNo(batchNo);
                detailMapper.insert(assetsScanResult);
                //TODO 扫描结果处理
                scanResultHandler(assetsScanResult);
            } else {
                Query query = new Query(CriteriaWrapper.where(AssetsScanResultInfo::getScanRuleId).is(id));
                query.addCriteria(CriteriaWrapper.where(AssetsScanResultInfo::getBatchNo).is(batchNo - 1));
                AssetsScanResultInfo detailMapperOne = detailMapper.findOne(query);
                if (null != detailMapperOne) {
                    List<ScanResultSuccessInfo> rawSuccessList = Optional.ofNullable(detailMapperOne.getSuccessList()).orElse(List.of());
                    List<ScanResultFailInfo> rawFailList = Optional.ofNullable(detailMapperOne.getFailList()).orElse(List.of());
                    rawSuccessList.addAll(assetsScanResult.getSuccessList());
                    rawFailList.addAll(assetsScanResult.getFailList());
                    rawSuccessList = rawSuccessList.stream().collect(Collectors.toMap(ScanResultSuccessInfo::getIpAddress, Function.identity(), (k1, k2) -> k1)).values().stream().toList();
                    rawFailList = rawFailList.stream().collect(Collectors.toMap(ScanResultFailInfo::getIpAddress, Function.identity(), (k1, k2) -> k1)).values().stream().toList();

                    Update update = new Update();
                    update.set(getFieldName(AssetsScanResultInfo::getSuccessList), rawSuccessList);
                    update.set(getFieldName(AssetsScanResultInfo::getFailList), rawFailList);
                    detailMapper.updateMulti(query, update);
                }

            }
            AssetsScanResultServiceImpl assetsScanResultService = (AssetsScanResultServiceImpl) cmdbServiceManage.getModelService(AssetsScanResultServiceImpl.ID);

            List<ScanResultSuccessInfo> successList = assetsScanResult.getSuccessList();
            //资产是否已添加判断处理
            assetsScanResultService.successDataExistCheck(successList);


            MwDiscoveryExecutorTypeEnum executorType = assetsDiscoverInfo.getExecutorType();
            //TODO 自动添加数据
            if (executorType != null && (autoManage.name().equals(executorType.name()) || autoAdd.name().equals(executorType.name()))) {
                List<ScanResultSuccessInfo> disList = successList.stream().filter(s -> s.getExist() == null || !s.getExist()).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(disList)) {
                    String customRuleId = assetsDiscoverInfo.getCustomRuleId();
                    MwCustomRuleServiceImpl customRuleService = (MwCustomRuleServiceImpl) cmdbServiceManage.getModelService(MwCustomRuleServiceImpl.ID);
                    MwCustomRuleInfo ruleInfo = customRuleService.doSelectById(customRuleId);

                    ModelAddTangAssetsParam param = new ModelAddTangAssetsParam();
                    BeanUtil.copyProperties(ruleInfo, param, CopyOptions.create().setIgnoreNullValue(true));
                    param.setEngineId(ruleInfo.getPollingEngineId());
                    List<InstanceInfo> instanceInfos = assetsScanResultService.instanceConvert(disList, param);
                    List<MwPropertyCustomParam> propertyInfos = ruleInfo.getPropertyInfos();
                    for (InstanceInfo instanceInfo : instanceInfos) {
                        for (MwPropertyCustomParam propertyParam : propertyInfos) {
                            instanceInfo.getData().put(propertyParam.getPropertyId(), propertyParam.getPropertyValue());
                        }
                    }
                    BatchAddAndUpdInstanceParam batchAddAndUpdInstanceParam = new BatchAddAndUpdInstanceParam();
                    batchAddAndUpdInstanceParam.setParams(instanceInfos);
                    cmdbServiceManage.service(batchAddAndUpdInstanceParam, ServiceAction.batchAdd);
                }

            }
//            //TODO 自动新增数据
//            if (executorType != null && autoAdd.name().equals(executorType)) {
//
//            }
            log.info("finish scan {},batchNo:{}", id, assetsScanResult.getBatchNo());
        } catch (InterruptedException interruptedException) {
            log.info("AssetsDiscoveryTask interrupt {}", id);
            assetsDiscoverInfo.setStatus(BatchScanStatusEnum.interrupt.name());
        } catch (Exception e) {
            log.error("扫描异常：", e);
            assetsDiscoverInfo.setStatus(BatchScanStatusEnum.fail.name());
        } finally {
            SecurityContextHolder.clearContext();
        }
        long endSecond = Instant.now().toEpochMilli();
        assetsDiscoverInfo.setScanStartDate(epochSecond);
        assetsDiscoverInfo.setScanEndDate(endSecond);

        //更新batchNo和扫描结果
        try {
            statusRefreshThread.interrupt();
            Query query = new Query(CriteriaWrapper.where(AssetsDiscoverInfo::getId).is(id));
            Update update = new Update();
            update.set(getFieldName(AssetsDiscoverInfo::getStatus), assetsDiscoverInfo.getStatus());
            update.set(getFieldName(AssetsDiscoverInfo::getScanStartDate), epochSecond);
            update.set(getFieldName(AssetsDiscoverInfo::getScanEndDate), endSecond);
            update.set(getFieldName(AssetsDiscoverInfo::getExecutor), userId);
            if (BatchScanStatusEnum.success.name().equals(assetsDiscoverInfo.getStatus())) {
                if (this.scanType) {
                    update.set(getFieldName(AssetsDiscoverInfo::getMaxBatchNo), batchNo);
                } else {
                    update.set(getFieldName(AssetsDiscoverInfo::getMaxBatchNo), batchNo - 1);
                }
            }
            assetsDiscoverMapper.updateMulti(query, update);
        } catch (Exception e) {
            log.error("assetsDiscoverMapper update", e);
        }

        runManage.finishDiscovery(assetsDiscoverInfo);
    }

    private AssetsScanResultInfo convertAssetsScanList(ScanResult scanResult) throws Exception {
        List<AssetsScanResultInfo> assetsScanResultList = Lists.newArrayList();
        Date nowLocalTime = BaseUtil.getNowLocalTime();
        ModuleIDManager moduleIDManager = new ModuleIDManager();
        // 添加扫描结果集
        AssetsScanResultInfo assetsScanResultInfo = new AssetsScanResultInfo();
        assetsScanResultInfo.setScanRuleId(scanResult.getTaskId());
        if (CollectionUtil.isNotEmpty(scanResult.getScanResultSuccesses())) {
            List<ScanResultSuccessInfo> successResultList = new ArrayList<>();
            scanResult.getScanResultSuccesses().forEach(item -> {
                ScanResultSuccessInfo info = new ScanResultSuccessInfo();
                BeanUtil.copyProperties(item, info);
                info.setId(strValueConvert(moduleIDManager.getID(IDModelType.AssetsScanResult)));
                info.setScanRuleId(scanResult.getTaskId());
                info.setSysObjectid(item.getSysObjectid());
                info.setIpLong(IpUtil.ipToLong(item.getIpAddress()));
                successResultList.add(info);
            });
            assetsScanResultInfo.setSuccessList(successResultList);
        }
        if (CollectionUtil.isNotEmpty(scanResult.getErrors())) {
            List<ScanResultFailInfo> failResultList = new ArrayList<>();
            scanResult.getErrors().forEach(item -> {
                ScanResultFailInfo info = new ScanResultFailInfo();
                BeanUtil.copyProperties(item, info);
                info.setScanRuleId(String.valueOf(scanResult.getTaskId()));
                info.setId(strValueConvert(moduleIDManager.getID(IDModelType.AssetsScanResult)));
                info.setIpAddress(item.getIpAddress());
                info.setProto(item.getProto());
                info.setIpLong(IpUtil.ipToLong(item.getIpAddress()));
                info.setFailCause(item.getCause());
                failResultList.add(info);
            });
            assetsScanResultInfo.setFailList(failResultList);
        }
        assetsScanResultInfo.setCreateTime(nowLocalTime);
        assetsScanResultList.add(assetsScanResultInfo);

        return assetsScanResultInfo;
    }

    private void scanResultHandler(AssetsScanResultInfo assetsScanResult) throws Exception {
        Date nowLocalTime = BaseUtil.getNowLocalTime();
        AssetsScanResultServiceImpl modelService = (AssetsScanResultServiceImpl) cmdbServiceManage.getModelService(AssetsScanResultServiceImpl.ID);
        AssetsInstanceScanSearchParam param = new AssetsInstanceScanSearchParam();
        param.setId(assetsScanResult.getScanRuleId());
        //获取所有历史版本的扫描数据
        List<AssetsScanTotalResultInfo> scanTotalResultInfos = modelService.getScanTotalResultInfos(assetsScanResult.getScanRuleId());

        List<ScanResultSuccessInfo> successAllList = new ArrayList<>();
        List<ScanResultFailInfo> failAllList = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(scanTotalResultInfos)) {

            for (AssetsScanTotalResultInfo resultInfo : scanTotalResultInfos) {
                successAllList.addAll(resultInfo.getSuccessList());
                failAllList.addAll(resultInfo.getFailList());
            }
        }
        Map<String, ScanResultSuccessInfo> successHistoryMap = new HashMap<>();
        for (ScanResultSuccessInfo successInfo : successAllList) {
            String ruleType = successInfo.getRuleType().getName();
            String ip = successInfo.getIpAddress();
            //Ip地址和扫描类型为唯一标识
            String key = ip + "_" + ruleType;
            successHistoryMap.put(key, successInfo);
        }

        //历史数据和当前数据匹配的key
        Set<String> checkMatchKey = new HashSet<>();
        //当前扫描的成功资产数据
        List<ScanResultSuccessInfo> successList = assetsScanResult.getSuccessList();
        for (ScanResultSuccessInfo successInfo : successList) {

            String ruleType = successInfo.getRuleType().getName();
            String ip = successInfo.getIpAddress();
            //Ip地址和扫描类型为唯一标识
            String key = ip + "_" + ruleType;
            //有历史数据时
            if (successHistoryMap != null && successHistoryMap.containsKey(key)) {
                checkMatchKey.add(key);
                ScanResultSuccessInfo oldDataInfo = successHistoryMap.get(key);
                Date scanFindTime = oldDataInfo.getScanFindTime();
                if (scanFindTime == null) {
                    successInfo.setScanFindTime(nowLocalTime);
                }else{
                    successInfo.setScanFindTime(scanFindTime);
                }
            } else {//无历史数据时，添加发现时间
                successInfo.setScanFindTime(nowLocalTime);
            }
            successInfo.setOnlineTime(nowLocalTime);
            successInfo.setOnlineStatus(online.getName());
        }
        //之前成功的历史数据（不在当前数据中，比如修改扫描规则,之前遗留的数据），也要保留
        if (successAllList.size() > checkMatchKey.size()) {
            List<ScanResultSuccessInfo> lastResultData = successAllList.stream().filter(s -> !checkMatchKey.contains(s.getIpAddress() + "_" + s.getRuleType().getName())).collect(Collectors.toList());
            for (ScanResultSuccessInfo lastInfo : lastResultData) {
                if (lastInfo.getScanFindTime() == null) {
                    lastInfo.setScanFindTime(lastInfo.getCreateDate());
                }
            }
            successList.addAll(lastResultData);

        }
        //当前扫描的失败资产数据
        List<ScanResultFailInfo> failList = assetsScanResult.getFailList();
        Iterator<ScanResultFailInfo> ite = failList.iterator();
        while (ite.hasNext()) {
            ScanResultFailInfo failInfo = ite.next();
            String ruleType = failInfo.getProto();
            String ip = failInfo.getIpAddress();
            //Ip地址和扫描类型为唯一标识
            String key = ip + "_" + ruleType;
            //成功的历史结果中含有该条失败数据，这条数据需要从当前的失败列表中去除，在成功列表中显示上一次扫描成功的该条数据
            if (successHistoryMap != null && successHistoryMap.containsKey(key)) {
                ScanResultSuccessInfo oldSuccessInfo = successHistoryMap.get(key);
                oldSuccessInfo.setOfflineTime(nowLocalTime);
                oldSuccessInfo.setOnlineStatus(offline.getName());
                successList.add(oldSuccessInfo);
            }
        }
        AssetsScanTotalResultInfo totalResultInfo = new AssetsScanTotalResultInfo();
        totalResultInfo.setId(assetsScanResult.getScanRuleId());
        if(assetsScanResult.getSuccessList()!=null){
            totalResultInfo.setSuccessList(assetsScanResult.getSuccessList());
        }
        if(assetsScanResult.getFailList()!=null){
            totalResultInfo.setFailList(assetsScanResult.getFailList());
        }
        Query query = new Query(CriteriaWrapper.where(AssetsScanTotalResultInfo::getId).is(totalResultInfo.getId()));
        Update update = CMDBTool.genMongoUpdate(totalResultInfo, null, null);
        totalResultMapper.upsert(query, update);
    }

}
